/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using Cobalt.Framework;
using Cobalt.Framework.Bsp;
using Cobalt.Framework.Graphics;

namespace Cobalt
{

	public class SampleGame : Microsoft.Xna.Framework.Game
	{
		private GraphicsDeviceManager graphics;

		public SampleGame()
		{
			graphics = new GraphicsDeviceManager( this );

			Content.RootDirectory = "Content";
			
#if DEBUG
			IsFixedTimeStep = false;
			graphics.SynchronizeWithVerticalRetrace = false;
#endif
		}

		private FrameStats frameStats;
		private PostProcess compositer;
		private MaterialManager materials;
		
		private Camera camera;

		protected override void Initialize()
		{
			frameStats = new FrameStats( this );
			Components.Add( frameStats );

			compositer = new PostProcess( this );
			Components.Add( compositer );

			compositer.Context.BlurSteps = 3;
			compositer.Context.BlurStrength = 5;
			compositer.Context.BloomThresh = 0.125F;
			compositer.Context.BloomSaturation = 1.25F;
			compositer.Context.BloomTint = new Vector3( 1.0F );
			compositer.Context.BaseSaturation = 1;
			compositer.Context.BaseTint = new Vector3( 1.25F );
			compositer.Context.PreferMultiSampling = true;

			materials = new MaterialManager( Services, Content );
			materials.RootDirectory = "Materials";
			Services.AddService( typeof( IMaterialService ), materials );

			camera = new Camera();

			base.Initialize();
		}

		private Matrix levelMatrix, inverseLevelMatrix;
		private ISurfaceList levelGeometry;
		private IPvs levelPvs;

		private BspData.Trace levelTrace;

		private DrawList drawList;
		private ShadingContext shadeCtx;

		private Vector4? spawnParams;

		protected override void LoadContent()
		{
			shadeCtx = new ShadingContext( GraphicsDevice );
			drawList = new DrawList( this.Services );

			levelGeometry = Content.Load<ISurfaceList>( @"chiroptera\maps\chiropteradm" );
			//levelGeometry = Content.Load<ISurfaceList>( @"mg_final\maps\mg_final" );
			//levelGeometry = Content.Load<ISurfaceList>( @"twq3tourney2\maps\twq3tourney2" );

			levelPvs = levelGeometry.CreatePvs();

			BspData bsp = levelGeometry as BspData;
			if( bsp != null )
			{
				foreach( Entity ent in bsp.Entities.All )
				{
					if( ent.ClassName.StartsWith( "info_player" ) )
					{
						spawnParams = new Vector4( ent.Origin, ent.Angle );
						break;
					}
				}

				levelTrace = bsp.CreateTrace();
			}

			levelMatrix = Matrix.CreateScale( 0.01F );
			Matrix.Invert( ref levelMatrix, out inverseLevelMatrix );
		}

		protected override void UnloadContent()
		{
		}

		public void MoveCamera( Vector2 move, Vector2 rotation, GameTime gameTime )
		{
			float time = (float)gameTime.ElapsedGameTime.TotalSeconds;

			if( rotation != Vector2.Zero )
			{
				Vector2 rv = Helpers.SmoothStep( Vector2.Zero, new Vector2( camera.RotationSpeed ), rotation * 0.25F ) * 4.0F;

				camera.Yaw -= rv.X * time * 1.5F;
				camera.Pitch -= rv.Y * time;
			}

			if( move.LengthSquared() > 0.05F )
			{
				Vector3 moveDir = new Vector3( move.X, 0, -move.Y );
				moveDir = Vector3.Transform( moveDir, camera.Oreintation );

				if( true )
				{
					camera.Position += moveDir * time * camera.MovementSpeed; ;
					return;
				}

				Vector3 moveRay = moveDir * time * camera.MovementSpeed;

				Vector3 traceStart = camera.Position;
				Vector3 traceEnd = camera.Position + moveRay;

				Vector3.Transform( ref traceStart, ref inverseLevelMatrix, out traceStart );
				Vector3.Transform( ref traceEnd, ref inverseLevelMatrix, out traceEnd );

				moveRay = traceEnd - traceStart;

				float sx = inverseLevelMatrix.Left.Length();
				float sy = inverseLevelMatrix.Up.Length();
				float sz = inverseLevelMatrix.Forward.Length();

				float maxScale = Math.Max( sx, Math.Max( sy, sz ) );

				BrushContents brushMask = BrushContents.Solid | BrushContents.PlayerClip;

				levelTrace.ContentMask = brushMask;

				BoundingSphere sphere = camera.ApertureBounds;
				sphere.Radius *= maxScale;

				levelTrace.TraceBox = BoundingBox.CreateFromSphere( sphere );
				levelTrace.Type = BspData.Trace.TraceType.Box;

				Vector3 newPos = traceStart + moveRay;

				const float TraceEps = 0.0F;

				while( levelTrace.TraceRay( traceStart, traceEnd ) )
				{
					Vector3 v = moveRay * Helpers.Saturate( levelTrace.HitFraction - TraceEps );

					if( levelTrace.HitFraction <= TraceEps )
					{
						newPos = traceStart + v;
						break;
					}

					//get the remaining motion
					Vector3 r = moveRay - v;
					//reduce it to the component parallel to the hit plane
					moveRay -= levelTrace.HitPlane.Normal * Vector3.Dot( levelTrace.HitPlane.Normal, r );

					//advance the position
					newPos = traceStart + v;
					traceStart = newPos;
					//and set up for the next trace
					traceEnd = traceStart + moveRay;
				}

				BspData bsp = levelGeometry as BspData;
				if( bsp != null )
				{
					BrushContents contents = bsp.GetPointContents( newPos ).GetValueOrDefault( BrushContents.None );
					if( (contents & brushMask) != BrushContents.None )
						return;
				}

				camera.Position = Vector3.Transform( newPos, levelMatrix );
			}
		}

		private void PrepForRender( GameTime gameTime )
		{
			shadeCtx.View = camera.View;
			shadeCtx.Projection = camera.Projection;
			shadeCtx.RenderTime = (float)gameTime.TotalRealTime.TotalSeconds;

			if( levelPvs != null )
			{
				Vector3 levelPos = Vector3.Transform( camera.Position, inverseLevelMatrix );
				levelPvs.Mark( levelPos );
			}

			drawList.Reset();

			if( levelGeometry != null )
				levelGeometry.AppendSurfaces( drawList, levelPvs, levelMatrix, shadeCtx );

			drawList.Sort();

			drawList.Prerender( shadeCtx );
		}

		protected override void Update( GameTime gameTime )
		{
			GamePadState inPad = GamePad.GetState( PlayerIndex.One );

			// Allows the game to exit
			if( inPad.Buttons.Back == ButtonState.Pressed )
				this.Exit();

			if( spawnParams.HasValue )
			{
				Vector3 spawnOr = new Vector3( spawnParams.Value.X, spawnParams.Value.Y, spawnParams.Value.Z );
				float spawnAngle = spawnParams.Value.W;

				camera.Position = Vector3.Transform( spawnOr, levelMatrix );
				camera.Yaw = MathHelper.ToDegrees( -spawnAngle + MathHelper.PiOver2 );

				spawnParams = null;
			}

			MoveCamera( inPad.ThumbSticks.Left, inPad.ThumbSticks.Right, gameTime );

			if( inPad.Buttons.LeftShoulder == ButtonState.Pressed )
				shadeCtx.OverrideMaterial = materials.GetMaterial( @"Debug\Wire" );
			else
				shadeCtx.OverrideMaterial = null;

			PrepForRender( gameTime );

			base.Update( gameTime );
		}

		protected override void Draw( GameTime gameTime )
		{
			PerformanceCounters counters = shadeCtx.PerformanceCounters;
			counters.Reset();

			compositer.Render( drawList, shadeCtx, null );

			if( counters != null )
			{
				//stats
				TextBuffer buf = frameStats.CustomText;

				buf.Clear();
				buf.AppendText( "Counters:\n  " );
				buf.AppendInt( counters.SurfacesDrawn, 6, ' ' );
				buf.AppendText( " Surfaces\n  " );
				buf.AppendInt( counters.VerticesDrawn, 6, ' ' );
				buf.AppendText( " Vertices\n  " );
				buf.AppendInt( counters.PrimitivesDrawn, 6, ' ' );
				buf.AppendText( " Primitives\n  " );
				buf.AppendInt( counters.DrawPrimitivesCount, 6, ' ' );
				buf.AppendText( " Draw Calls\n  " );
			}

			base.Draw( gameTime );
		}
	}
}
